// Package verifycode 用以发送手机验证码和邮箱验证码
package verifycode

import (
	"context"
	"encoding/json"
	"git.mycaigou.com/gfyx/common/cache"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/common/msg_center"
	"git.mycaigou.com/gfyx/micro-gfyx-oppty-service/infrastructure/common/utils"
	"git.myscrm.cn/golang/common/ykenv"
	"git.myscrm.cn/golang/stark/v4"
	"strconv"
)

const sms_locked_key = "common:sms:locked:"
const sms_send_cnt_key = "common:sms:cnt:"
const sms_send_max_day = 30 // 单个手机号一天发送最多 30 次

type VerifyCode struct {
	Store       Store
	ctx         context.Context
	redisClient *cache.TRedis
}

// NewVerifyCode 单例模式获取
func NewVerifyCode(ctx context.Context) *VerifyCode {
	redisClient, _ := cache.TenantRedis()
	return &VerifyCode{
		Store: &RedisStore{
			RedisClient: redisClient,
			// 增加前缀保持数据库整洁，出问题调试时也方便
			KeyPrefix: "common:verifycode:",
			ctx:       ctx,
		},
		ctx:         ctx,
		redisClient: redisClient,
	}
}

// SendSMS 发送验证码
func (vc *VerifyCode) SendSMS(phone string) (string, error) {

	// 生成验证码
	code := vc.generateVerifyCode(phone)

	// 方便本地和 API 自动测试
	if ykenv.IsDevMode() {
		//return code, nil
	}

	// 发送短信
	msgClient := msg_center.NewClient(nil)
	receiverIds := []string{phone}
	content, _ := json.Marshal(map[string]interface{}{"verify_code": code})
	// todo 平台推送可能会延迟，在验证码场景下是致命的
	msgStd := &msg_center.MsgBody{
		PushOrderCode: stark.Configer.(stark.ApolloConfigerIface).GetStringValue("PushOrderCode.VerifyCode", ""),
		Sms: &msg_center.MsgContent{
			ReceiverIds: receiverIds,
			Content:     string(content),
			TryTimes:    2,
			ConfirmUrl:  "",
			CallbackUrl: "",
		},
	}

	stark.Logger.Infof(vc.ctx, "send-verify-code phone:%s, code: %s", phone, code)

	if err := msgClient.Push(vc.ctx, msgStd); err != nil {
		stark.Logger.Errorf(vc.ctx, "send-verify-code error:%s", err.Error())
		return "", err
	}
	return code, nil
}

// CheckAnswer 检查用户提交的验证码是否正确，key 可以是手机号或者 Email
func (vc *VerifyCode) CheckAnswer(key string, answer string) bool {

	stark.Logger.Infof(vc.ctx, "check-verify-code key:%s, answer: %s", key, answer)

	// 方便开发，开发环境会直接验证成功
	if ykenv.IsDevMode() || ykenv.IsTestMode() {
		return true
	}

	// clear true 时，只能验证一次
	res := vc.Store.Verify(key, answer, false)
	// 如果正确的话删除，不正确验证码继续保留
	if res {
		vc.Store.Get(key, true)
	}
	return res
}

// generateVerifyCode 生成验证码，并放置于 Redis 中
func (vc *VerifyCode) generateVerifyCode(key string) string {

	// 生成随机码 默认 6 位
	code := utils.RandomNumber(6)

	// 为方便开发，本地环境使用固定验证码
	if ykenv.IsDevMode() {
		//code = "123456"
	}

	// 将验证码及 KEY（邮箱或手机号）存放到 Redis 中并设置过期时间
	vc.Store.Set(key, code)
	return code
}

// GetSendSmsCnt 获取用户发送次数
func (vc *VerifyCode) GetSendSmsCnt(ctx context.Context, mobile string) int64 {
	cacheKey := sms_send_cnt_key + mobile
	cnt, _ := vc.redisClient.Get(ctx, cacheKey)
	cntNum := 0
	if cnt != "" {
		cntNum, _ = strconv.Atoi(cnt)
	}

	return int64(cntNum)
}

func (vc *VerifyCode) IncrSendSmsCnt(ctx context.Context, mobile string) int64 {
	cacheKey := sms_send_cnt_key + mobile
	cnt, _ := vc.redisClient.Get(ctx, cacheKey)
	if cnt != "" {
		incr, _ := vc.redisClient.Incr(ctx, cacheKey)
		return incr
	} else {
		// 一天
		_, _ = vc.redisClient.Set(ctx, cacheKey, "1", true, 86400)
		return 1
	}
}

func (vc *VerifyCode) IsLocked(ctx context.Context, mobile string) bool {
	cacheKey := sms_locked_key + mobile
	status, _ := vc.redisClient.Get(ctx, cacheKey)
	if status == "locked" {
		return true
	} else {
		// 发送次数已经大于指定次数，则锁定 30 分钟
		cnt := vc.GetSendSmsCnt(ctx, mobile)
		if cnt >= sms_send_max_day {
			stark.Logger.Infof(ctx, "mobile is locked, cache key:%s", cacheKey)
			_, _ = vc.redisClient.Set(ctx, cacheKey, "locked", true, 86400)
			return true
		}
	}
	return false
}
